package com.urbanski.clipboard.client;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.urbanski.clipboard.client.threads.ClientCommandLineListener;
import com.urbanski.clipboard.client.threads.ClipboardListener;
import com.urbanski.clipboard.client.util.ClientConstants.ClientCommand;
import com.urbanski.clipboard.client.util.ClientUtils;
import com.urbanski.clipboard.common.exception.InvalidCommandException;
import com.urbanski.util.io.FileUtils;
import com.urbanski.util.logging.LoggingUtil;

public class ClipboardClient
{
	private static final Logger LOGGER = Logger.getLogger(ClipboardClient.class);

	private static ClipboardListener clipboardListener;
	private static ClientCommandLineListener commandLineListener;
	private static Map<String, ServerConnection> serverConnections = new HashMap<>();
	private static String clientName;
	private static String startupScript;

	public static void main(String[] args) throws InterruptedException, IOException
	{
		LOGGER.trace("Entering ClipboardClient.main");
		
		System.out.println("Enter name: ");
		clientName = new Scanner(System.in).nextLine();

		if (!init(args))
		{
			return;
		}
		// printHelp();

		synchronized (ClipboardClient.class)
		{
//			clipboardListener = new ClipboardListener();
//			Thread clipboardListenerThread = new Thread(clipboardListener);
//			clipboardListenerThread.start();

			commandLineListener = new ClientCommandLineListener();
			Thread commandLineListenerThread = new Thread(commandLineListener);
			commandLineListenerThread.start();
		}

		LOGGER.trace("Exiting ClipboardClient.main");
	}

	public static String getClientName()
	{
		return clientName;
	}

	public static ClipboardListener getClipboardListener()
	{
		return clipboardListener;
	}

	public static ClientCommandLineListener getCommandLineListener()
	{
		return commandLineListener;
	}

	public static ServerConnection findConnection(String alias)
	{
		synchronized (serverConnections)
		{
			return serverConnections.get(alias);
		}
	}

	public static ServerConnection addConnection(String hostname, int port)
	{
		return addConnection(hostname, port, null);
	}

	public static ServerConnection addConnection(String hostname, int port, String alias)
	{
		synchronized (serverConnections)
		{
			ServerConnection serverConnection = connectionExists(hostname);
			if (serverConnection != null)
			{
				LoggingUtil.logAndPrintError(LOGGER, "Connection to " + hostname + " already exists (alias " + (alias == null ? "not defined" : serverConnection.getAlias()) + ')');
			}

			try
			{
				serverConnection = new ServerConnection(hostname, port, alias);
				serverConnection.connect();
				serverConnections.put(alias == null ? hostname : alias, serverConnection);
				return serverConnection;
			}
			catch (IOException e)
			{
				LoggingUtil.logAndPrintError(LOGGER, "Error establishing a connection with " + hostname + " on port " + port, e);
			}

			return null;
		}
	}

	private static ServerConnection connectionExists(String hostname)
	{
		synchronized (serverConnections)
		{
			ServerConnection sc = serverConnections.get(hostname);
			if (sc != null)
			{
				return sc;
			}

			for (ServerConnection serverConnection : serverConnections.values())
			{
				if (serverConnection.getHostname().equals(hostname))
				{
					sc = serverConnection;
					break;
				}
			}

			return sc;
		}
	}
	
	public static void disconnect(String alias)
	{
		ServerConnection serverConnection = serverConnections.get(alias);
		if (serverConnection != null)
		{
			serverConnections.remove(alias);
			serverConnection.close();
		}
		else
		{
			LoggingUtil.logAndPrintError(LOGGER, "No active connection with the name " + alias);
		}
	}
	
	public static void disconnectAll()
	{
		for (String alias : serverConnections.keySet())
		{
			ClientUtils.executeCommand("disconnect " + alias);
		}
	}
	
	public static void setAlias(String alias, String newAlias)
	{
		ServerConnection serverConnection = serverConnections.get(alias);
		if (serverConnection != null)
		{
			serverConnection.setAlias(newAlias);
			serverConnections.remove(alias);
			
			if (newAlias == null)
			{
				newAlias = serverConnection.getHostname();
			}
			
			System.out.println("Setting alias of " + alias + " to " + newAlias);
			
			serverConnections.put(newAlias, serverConnection);
		}
		else
		{
			LoggingUtil.logAndPrintWarning(LOGGER, "No active connection with alias " + alias);
		}
	}
	
	public static Map<String, ServerConnection> getConnections()
	{
		return serverConnections;
	}

	private static boolean init(String[] args)
	{
		LOGGER.trace("Entering ClipboardClient.init");

		if (!processArgs(args))
		{
			System.out.println("Invalid command line arguments specified.  Please review the following help text for guidance:\n");
			ClientUtils.printHelp();
			return false;
		}

		if (startupScript != null)
		{
			executeScript(startupScript);
		}

		LOGGER.trace("Exiting ClipboardClient.init");
		return true;
	}

	private static boolean processArgs(String[] args)
	{
		LOGGER.trace("Entering ClipboardClient.processArgs");

		boolean error = false;

		for (int i = 0; i < args.length; i++)
		{
			if (args[i].equals("-n"))
			{
				if (i < args.length - 1)
				{
					clientName = args[i + 1];
					i++;
				}
				else
				{
					System.err.println("Error: no client name specified.");
					error = true;
					break;
				}
			}
			else if (args[i].equals("-s"))
			{
				if (i < args.length - 1)
				{
					startupScript = args[i + 1];
					i++;
				}
				else
				{
					System.err.println("The -s option was specified but no script was provided.  Skipping startup script.");
				}
			}
		}

		if (!error && clientName == null)
		{
			System.err.println("Error: no client name specified.");
			error = true;
		}
		
		LOGGER.trace("Exiting ClipboardClient.processArgs");
		return !error;
	}

	private static void executeScript(String script)
	{
		LOGGER.trace("Entering ClipboardClient.executeScript");

		LoggingUtil.logAndPrintInfo(LOGGER, "Executing startup script: " + script);
		
		BufferedReader reader = null;
		
		try
		{
			reader = new BufferedReader(new FileReader(script));
			String line;
			
			while ((line = reader.readLine()) != null)
			{
				LoggingUtil.logAndPrintDebug(LOGGER, line);

				String[] tokens = StringUtils.split(line);

				try
				{
					ClientCommand command = ClientCommand.getClientCommand(tokens[0]);

					ClientUtils.processCommand(command, line, tokens);
				}
				catch (InvalidCommandException e)
				{
					LoggingUtil.logAndPrintError(LOGGER, e.getMessage());
					return;
				}
				catch (Exception e)
				{
					LoggingUtil.logAndPrintError(LOGGER, "Unexpected exception occured while executing startup script", e);
					return;
				}
			}
		}
		catch (IOException e)
		{
			LoggingUtil.logAndPrintError(LOGGER, "Error reading startup script", e);
		}
		finally
		{
			FileUtils.close(reader);
		}

		LOGGER.trace("Exiting ClipboardClient.executeScript");
	}
}
